package com.cheegu.common.parser.gpsdata.unpacker;

import com.cheegu.common.parser.gpsdata.Unpacker;
import com.cheegu.common.parser.gpsdata.util.BitUtils;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Date;

public class DefaultUnpacker implements Unpacker {
    DataInputStream dis = null;

    public DefaultUnpacker(byte[] bytes) {
        this(0, bytes);
    }

    public DefaultUnpacker(int curIndex, byte[] bytes) {
        if (curIndex < 0) {
            throw new IllegalArgumentException("curIndex");
        }
        if (bytes == null) {
            throw new IllegalArgumentException("bytes");
        }

        this.dis = new DataInputStream(new ByteArrayInputStream(bytes));
        try {
            dis.skipBytes(curIndex);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int readInt32() throws IOException {
        final int INT32_LENGTH = 4;
        if (dis.available() >= INT32_LENGTH) {
            return dis.readInt();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public long readUInt32() throws IOException {
        final int UINT32_LENGTH = 4;
        if (dis.available() >= UINT32_LENGTH) {
            return unsigned(dis.readInt());
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public long readInt64() throws IOException {
        final int INT64_LENGTH = 8;
        if (dis.available() >= INT64_LENGTH) {
            return dis.readLong();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public BigInteger readUInt64() throws IOException {
        final int UINT64_LENGTH = 8;
        if (dis.available() >= UINT64_LENGTH) {
            byte[] tmp = new byte[8];
            if (dis.read(tmp) != -1) {
                return unsigned(new BigInteger(tmp));
            }
        }
        throw new IndexOutOfBoundsException();
    }

    @Override
    public Date readDateTime() throws IOException {
        final int DATETIME_LENGTH = 8;
        if (dis.available() >= DATETIME_LENGTH) {
            long timestamp = dis.readLong();
            return new Date(timestamp);
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public byte readByte() throws IOException {
        final int BYTE_LENGTH = 1;
        if (dis.available() >= BYTE_LENGTH) {
            return dis.readByte();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public short readUByte() throws IOException {
        final int UBYTE_LENGTH = 1;
        if (dis.available() >= UBYTE_LENGTH) {
            return (short) dis.readUnsignedByte();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public byte[] readBytes(int length) throws IOException {
        if (dis.available() >= length) {
            byte[] bytes = new byte[length];
            if (dis.read(bytes) > 0) {
                return bytes;
            }
        }
        throw new IndexOutOfBoundsException();
    }

    @Override
    public double readDouble() throws IOException {
        final int DOUBLE_LENGTH = 8;
        if (dis.available() >= DOUBLE_LENGTH) {
            return dis.readDouble();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public BigDecimal readDecimal() throws IOException {
        final int DECIMAL_LENGTH = 16;
        if (dis.available() >= DECIMAL_LENGTH) {
            byte[] tmp = new byte[DECIMAL_LENGTH];
            if (dis.read(tmp) != -1) {
                return new BigDecimal(dis.read(tmp));
            }
        }
        throw new IndexOutOfBoundsException();
    }

    @Override
    public short readShort() throws IOException {
        final int SHORT_LENGTH = 2;
        if (dis.available() >= SHORT_LENGTH) {
            return dis.readShort();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public int readUShort() throws IOException {
        final int USHORT_LENGTH = 2;
        if (dis.available() >= USHORT_LENGTH) {
            return dis.readUnsignedShort();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public float readFloat() throws IOException {
        final int FLOAT_LENGTH = 2;
        if (dis.available() >= FLOAT_LENGTH) {
            return dis.readFloat();
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    @Override
    public String readASCIIString(int length) throws IOException {
        return readString(length, "ascii");
    }

    @Override
    public String readGBKString(int length) throws IOException {
        return readString(length, "gbk");
    }

    public int available() {
        try {
            return dis.available();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String readString(int length, String charsetName) throws IOException {
        if (dis.available() >= length) {
            byte[] tmp = new byte[length];
            if (dis.read(tmp) != -1) {
                return new String(tmp, charsetName);
            }
        }
        throw new IndexOutOfBoundsException();
    }

    private long unsigned(int i) {
        if (i >= 0)
            return i;
        return BitUtils.bytes2long(BitUtils.int2bytes(i));
    }

    private BigInteger unsigned(BigInteger bint) {
        if (bint.signum() >= 0)
            return bint;
        byte[] bytes = bint.toByteArray();
        byte[] a1 = new byte[8];
        System.arraycopy(bytes, 0, a1, 8 - bytes.length, bytes.length);
        Arrays.fill(a1, 0, 8 - bytes.length, (byte) -1);

        byte[] a2 = new byte[a1.length + 1];
        a2[0] = 0;
        System.arraycopy(a1, 0, a2, 1, a1.length);
        return new BigInteger(a2);
    }
}
